Flutter Forms
Flutter Forms provide a structured way to collect, validate, manage, and submit user input. Forms are commonly used for login screens, registration pages, checkout forms, profile editing, feedback forms, search screens, and other data-entry features. Flutter provides the Form, FormField, and TextFormField widgets to build and validate forms. The Form widget groups related form fields and provides methods such as validate(), save(), and reset(). :contentReference[oaicite:0]{index=0}
1. What is a Form in Flutter?
A form is a user interface section that contains one or more input fields where users enter information. Flutter's Form widget acts as a container for grouping form fields and managing their state.
Common Examples of Forms
- Login form
- Registration form
- Contact form
- Feedback form
- Profile form
- Address form
- Checkout form
- Job application form
- Search form
- Payment information form
Basic Structure
Form(
child: Column(
children: [
TextFormField(),
ElevatedButton(
onPressed: () {},
child: const Text('Submit'),
),
],
),
)
2. Important Flutter Form Widgets
| Widget | Purpose |
|---|
Form | Groups multiple form fields and manages form state. |
FormField | Represents an individual form field with state and validation support. |
TextFormField | Provides a text input field integrated with Form. |
TextEditingController | Reads and controls the text entered into a text field. |
GlobalKey | Provides access to the form's state for operations such as validation, saving, and resetting. |
FocusNode | Controls focus between input fields. |
TextFormField wraps a text field in a FormField, making it convenient to use validation and other form-related functionality. :contentReference[oaicite:1]{index=1}
3. TextField vs TextFormField
Flutter provides both TextField and TextFormField for text input. TextField is suitable for general text input, while TextFormField integrates with a surrounding Form and is commonly used when validation is required. :contentReference[oaicite:2]{index=2}
| Feature | TextField | TextFormField |
|---|
| Text input | Yes | Yes |
| Form integration | No direct Form integration | Yes |
| validator property | No | Yes |
| onSaved | No | Yes |
| Form reset support | No direct support | Yes |
| Best suited for | General input | Structured forms |
4. Basic Form Structure
Form(
child: Column(
children: [
TextFormField(),
TextFormField(),
ElevatedButton(
onPressed: () {},
child: const Text('Submit'),
),
],
),
)
In a real application, the form normally uses a GlobalKey so that the application can validate, save, or reset the form.
5. Using GlobalKey with Form
A GlobalKey provides access to the current FormState. Flutter's documentation recommends a GlobalKey as a straightforward way to access the form state. :contentReference[oaicite:3]{index=3}
final GlobalKey formKey =
GlobalKey();
Form(
key: formKey,
child: Column(
children: [
TextFormField(),
],
),
)
6. Form Validation
Validation checks whether the information entered by the user satisfies the application's requirements. The validator function returns an error message when the input is invalid and returns null when the input is valid. :contentReference[oaicite:4]{index=4}
Example
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your name';
}
return null;
},
)
How Validation Works
- The user enters information.
- The user presses the Submit button.
- The application calls
formKey.currentState!.validate().
- Flutter runs the validator for each form field.
- If a validator returns an error message, that error is displayed.
- If all validators return
null, the form is considered valid.
7. Calling validate()
if (formKey.currentState!.validate()) {
print('Form is valid');
} else {
print('Form contains errors');
}
The validate() method runs the validators of the form fields and returns true when there are no validation errors. :contentReference[oaicite:5]{index=5}
8. Complete Basic Form Example
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const UserFormScreen(),
);
}
}
class UserFormScreen extends StatefulWidget {
const UserFormScreen({super.key});
@override
State createState() => _UserFormScreenState();
}
class _UserFormScreenState extends State {
final GlobalKey formKey =
GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('User Form'),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Form(
key: formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null ||
value.trim().isEmpty) {
return 'Please enter your name';
}
return null;
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Form submitted'),
),
);
}
},
child: const Text('Submit'),
),
],
),
),
),
);
}
}
9. Required Field Validation
Required field validation ensures that a user does not leave an important field empty.
TextFormField(
decoration: const InputDecoration(
labelText: 'Full Name',
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Full name is required';
}
return null;
},
)
10. Email Validation
Email fields can be validated before submitting the form.
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Email is required';
}
if (!value.contains('@')) {
return 'Enter a valid email';
}
return null;
},
)
More Structured Email Check
bool isValidEmail(String email) {
return RegExp(
r'^[^@\s]+@[^@\s]+\.[^@\s]+$',
).hasMatch(email);
}
TextFormField(
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Enter your email';
}
if (!isValidEmail(value.trim())) {
return 'Enter a valid email';
}
return null;
},
)
11. Password Validation
TextFormField(
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
if (value.length < 8) {
return 'Password must contain at least 8 characters';
}
return null;
},
)
12. Confirm Password Validation
When a form contains password and confirm-password fields, the second field can be compared with the first field.
final TextEditingController passwordController =
TextEditingController();
TextFormField(
controller: passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
),
)
TextFormField(
obscureText: true,
decoration: const InputDecoration(
labelText: 'Confirm Password',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please confirm your password';
}
if (value != passwordController.text) {
return 'Passwords do not match';
}
return null;
},
)
13. Phone Number Validation
TextFormField(
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
labelText: 'Phone Number',
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Phone number is required';
}
if (value.trim().length < 10) {
return 'Enter a valid phone number';
}
return null;
},
)
14. TextEditingController in Forms
TextEditingController can be connected to a TextFormField when the application needs to read or modify the field value programmatically.
final TextEditingController nameController =
TextEditingController();
TextFormField(
controller: nameController,
decoration: const InputDecoration(
labelText: 'Name',
),
)
Reading the Value
String name = nameController.text.trim();
When controllers are created by a stateful widget, they should be disposed when no longer needed. :contentReference[oaicite:6]{index=6}
15. Form onSaved Callback
The onSaved callback can be used to store a field's final value when FormState.save() is called.
String name = '';
TextFormField(
onSaved: (value) {
name = value?.trim() ?? '';
},
)
Calling save()
if (formKey.currentState!.validate()) {
formKey.currentState!.save();
print(name);
}
FormState.save() invokes the onSaved callback of each form field. :contentReference[oaicite:7]{index=7}
16. Resetting a Form
The reset() method resets the form fields to their initial values and resets their validation state.
formKey.currentState!.reset();
Reset Button
OutlinedButton(
onPressed: () {
formKey.currentState!.reset();
},
child: const Text('Reset'),
)
17. AutovalidateMode
autovalidateMode controls when validation should automatically occur.
| Mode | Description |
|---|
AutovalidateMode.disabled | Automatic validation is disabled. |
AutovalidateMode.always | Validation runs automatically. |
AutovalidateMode.onUserInteraction | Validation responds to user interaction. |
Example
TextFormField(
autovalidateMode:
AutovalidateMode.onUserInteraction,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your name';
}
return null;
},
)
18. Form-Level onChanged
The Form widget can also receive an onChanged callback when a form field changes.
Form(
key: formKey,
onChanged: () {
print('Form changed');
},
child: Column(
children: [
TextFormField(),
TextFormField(),
],
),
)
19. Handling Different Input Types
Name
TextFormField(
textCapitalization: TextCapitalization.words,
decoration: const InputDecoration(
labelText: 'Full Name',
),
)
Email
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
),
)
Phone
TextFormField(
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
labelText: 'Phone',
),
)
Number
TextFormField(
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Age',
),
)
Multiline Text
TextFormField(
maxLines: 5,
decoration: const InputDecoration(
labelText: 'Message',
alignLabelWithHint: true,
),
)
20. Password Visibility Control
bool isPasswordVisible = false;
TextFormField(
obscureText: !isPasswordVisible,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: IconButton(
icon: Icon(
isPasswordVisible
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
isPasswordVisible = !isPasswordVisible;
});
},
),
),
)
21. Input Formatting
Input formatters can restrict or transform user input before it is processed.
import 'package:flutter/services.dart';
TextFormField(
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
decoration: const InputDecoration(
labelText: 'PIN Code',
),
)
Limit Input Length
TextFormField(
inputFormatters: [
LengthLimitingTextInputFormatter(10),
],
)
22. Form with Multiple Fields
Form(
key: formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(
labelText: 'Name',
),
),
TextFormField(
decoration: const InputDecoration(
labelText: 'Email',
),
),
TextFormField(
decoration: const InputDecoration(
labelText: 'Phone',
),
),
TextFormField(
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
),
),
],
),
)
23. Complete Registration Form
import 'package:flutter/material.dart';
class RegistrationForm extends StatefulWidget {
const RegistrationForm({super.key});
@override
State createState() =>
_RegistrationFormState();
}
class _RegistrationFormState
extends State {
final formKey = GlobalKey();
final nameController = TextEditingController();
final emailController = TextEditingController();
final phoneController = TextEditingController();
final passwordController = TextEditingController();
final confirmPasswordController = TextEditingController();
@override
void dispose() {
nameController.dispose();
emailController.dispose();
phoneController.dispose();
passwordController.dispose();
confirmPasswordController.dispose();
super.dispose();
}
void register() {
if (!formKey.currentState!.validate()) {
return;
}
final name = nameController.text.trim();
final email = emailController.text.trim();
final phone = phoneController.text.trim();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Welcome, $name'),
),
);
print('Name: $name');
print('Email: $email');
print('Phone: $phone');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Registration'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Form(
key: formKey,
child: Column(
children: [
TextFormField(
controller: nameController,
textCapitalization:
TextCapitalization.words,
decoration: const InputDecoration(
labelText: 'Full Name',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null ||
value.trim().isEmpty) {
return 'Enter your full name';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: emailController,
keyboardType:
TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null ||
value.trim().isEmpty) {
return 'Enter your email';
}
if (!value.contains('@')) {
return 'Enter a valid email';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: phoneController,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
labelText: 'Phone',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null ||
value.trim().isEmpty) {
return 'Enter your phone number';
}
if (value.trim().length < 10) {
return 'Enter a valid phone number';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter a password';
}
if (value.length < 8) {
return 'Password must have at least 8 characters';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: confirmPasswordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Confirm Password',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Confirm your password';
}
if (value != passwordController.text) {
return 'Passwords do not match';
}
return null;
},
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: register,
child: const Text('Register'),
),
),
const SizedBox(height: 10),
OutlinedButton(
onPressed: () {
formKey.currentState!.reset();
nameController.clear();
emailController.clear();
phoneController.clear();
passwordController.clear();
confirmPasswordController.clear();
},
child: const Text('Clear Form'),
),
],
),
),
),
);
}
}
24. Focus Management in Forms
Forms can contain several input fields. Focus management allows the user to move from one field to another efficiently. Flutter provides FocusNode for managing focus. :contentReference[oaicite:8]{index=8}
Creating FocusNodes
final nameFocus = FocusNode();
final emailFocus = FocusNode();
Connecting FocusNode
TextFormField(
focusNode: nameFocus,
)
Moving Focus
FocusScope.of(context).requestFocus(emailFocus);
Using TextInputAction
TextFormField(
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) {
FocusScope.of(context).requestFocus(emailFocus);
},
)
25. Disposing FocusNodes
Focus nodes are long-lived objects and should be disposed when they are no longer needed.
@override
void dispose() {
nameFocus.dispose();
emailFocus.dispose();
super.dispose();
}
26. Login Form Example
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
@override
State createState() => _LoginFormState();
}
class _LoginFormState extends State {
final formKey = GlobalKey();
final emailController = TextEditingController();
final passwordController = TextEditingController();
@override
void dispose() {
emailController.dispose();
passwordController.dispose();
super.dispose();
}
void login() {
if (!formKey.currentState!.validate()) {
return;
}
final email = emailController.text.trim();
final password = passwordController.text;
print('Email: $email');
print('Password: $password');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Login successful'),
),
);
}
@override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
children: [
TextFormField(
controller: emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
),
validator: (value) {
if (value == null ||
value.trim().isEmpty) {
return 'Enter your email';
}
return null;
},
),
TextFormField(
controller: passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your password';
}
return null;
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: login,
child: const Text('Login'),
),
],
),
);
}
}
27. Dropdown Form Field
Forms are not limited to text fields. Selection controls can also participate in form validation.
String? selectedRole;
DropdownButtonFormField(
decoration: const InputDecoration(
labelText: 'Role',
border: OutlineInputBorder(),
),
items: const [
DropdownMenuItem(
value: 'student',
child: Text('Student'),
),
DropdownMenuItem(
value: 'developer',
child: Text('Developer'),
),
DropdownMenuItem(
value: 'designer',
child: Text('Designer'),
),
],
onChanged: (value) {
selectedRole = value;
},
validator: (value) {
if (value == null) {
return 'Please select a role';
}
return null;
},
)
28. Checkbox Form Field
Checkboxes can be used for accepting terms, preferences, or other boolean choices.
bool acceptedTerms = false;
CheckboxListTile(
value: acceptedTerms,
title: const Text('I agree to the terms'),
onChanged: (value) {
setState(() {
acceptedTerms = value ?? false;
});
},
)
Validation Before Submission
if (!acceptedTerms) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please accept the terms'),
),
);
return;
}
29. Date Selection in a Form
A form may also collect a date such as date of birth, appointment date, or delivery date.
DateTime? selectedDate;
Future selectDate(BuildContext context) async {
final date = await showDatePicker(
context: context,
firstDate: DateTime(1950),
lastDate: DateTime.now(),
initialDate: DateTime.now(),
);
if (date != null) {
setState(() {
selectedDate = date;
});
}
}
30. Saving Form Data
Form data can be collected after validation.
void saveForm() {
if (!formKey.currentState!.validate()) {
return;
}
formKey.currentState!.save();
print('Form data saved');
}
A common flow is:
Validate
↓
Save
↓
Read/Process Data
↓
Send to API or Database
↓
Show Success/Error Message
31. Form Submission with API
After validation, form data can be sent to an API.
Future submitForm() async {
if (!formKey.currentState!.validate()) {
return;
}
final name = nameController.text.trim();
final email = emailController.text.trim();
print('Sending data...');
print('Name: $name');
print('Email: $email');
// API request can be performed here.
}
Always validate user input before sending it to a backend service.
32. Handling Form Loading State
When a form performs an asynchronous operation such as an API request, a loading state can prevent repeated submissions.
bool isLoading = false;
Future submitForm() async {
if (!formKey.currentState!.validate()) {
return;
}
setState(() {
isLoading = true;
});
try {
await Future.delayed(
const Duration(seconds: 2),
);
print('Form submitted');
} finally {
if (mounted) {
setState(() {
isLoading = false;
});
}
}
}
Button
ElevatedButton(
onPressed: isLoading ? null : submitForm,
child: isLoading
? const CircularProgressIndicator()
: const Text('Submit'),
)
33. Form Error Handling
Validation errors and server errors should be handled separately.
| Error Type | Example | Handling |
|---|
| Required field | Name is empty | Validator |
| Invalid format | Email is invalid | Validator |
| Password mismatch | Passwords differ | Validator |
| Network error | Request failed | Try/catch and user message |
| Server validation | Email already exists | Display server response |
34. Common Form Mistakes
Mistake 1: Not Using a Form Key
Without an appropriate form-state access method, validating the entire form becomes less convenient.
Mistake 2: Forgetting Validators
Collecting input without validation can result in incomplete or invalid data.
Mistake 3: Forgetting dispose()
@override
void dispose() {
controller.dispose();
super.dispose();
}
Mistake 4: Creating Controllers Inside build()
Controllers should generally be maintained in the state that owns them rather than recreated during every rebuild.
Mistake 5: Not Handling Loading State
For asynchronous submissions, disable or otherwise guard the submit action while the request is in progress.
Mistake 6: Trusting Client-Side Validation Alone
Client-side validation improves user experience, but backend systems should independently validate data received from clients.
35. Form Best Practices
- Use
Form to group related form fields.
- Use
TextFormField when validation is required.
- Use
GlobalKey when convenient access to form state is needed.
- Keep validation messages short and understandable.
- Use appropriate keyboard types for different input fields.
- Use
TextEditingController when direct access to input values is required.
- Dispose controllers and focus nodes owned by stateful widgets.
- Use input formatters for input restrictions.
- Use
trim() where leading and trailing spaces should not be significant.
- Prevent duplicate submissions during asynchronous operations.
- Do not print passwords or other sensitive information in production logs.
- Validate important data on the backend as well.
- Use
SingleChildScrollView or another suitable scrolling layout when forms may exceed the available screen height.
36. Responsive Form Layout
Forms should work on different screen sizes. A simple approach is to constrain the form width on larger screens.
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 500,
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Form(
key: formKey,
child: Column(
children: [
TextFormField(),
const SizedBox(height: 16),
ElevatedButton(
onPressed: submitForm,
child: const Text('Submit'),
),
],
),
),
),
),
)
37. Form with Scrollable Content
Long forms can cause overflow on smaller screens or when the keyboard is displayed. A scrollable parent can help the user access all fields.
Scaffold(
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Form(
key: formKey,
child: Column(
children: [
TextFormField(),
TextFormField(),
TextFormField(),
TextFormField(),
TextFormField(),
],
),
),
),
),
)
38. Practical Project: Contact Form
Create a contact form containing:
- Name
- Email
- Phone number
- Subject
- Message
- Submit button
- Reset button
Suggested Validation
- Name must not be empty.
- Email must have a valid format.
- Phone number must contain the required number of digits.
- Subject must not be empty.
- Message must contain a minimum number of characters.
39. Practical Exercise
Create a complete Student Registration Form with the following fields:
- Student Name
- Email
- Phone Number
- Date of Birth
- Course Selection
- Password
- Confirm Password
- Terms and Conditions checkbox
- Register button
- Reset button
Requirements
- Use a
Form.
- Use
GlobalKey.
- Use
TextFormField for text inputs.
- Use validators for required fields.
- Validate email format.
- Validate password length.
- Compare password and confirm password.
- Require terms acceptance.
- Use a dropdown for course selection.
- Use a date picker for date of birth.
- Show a success message after successful validation.
- Reset the form using
reset().
- Dispose all controllers and focus nodes.
40. Form Lifecycle
User Opens Form
↓
User Enters Data
↓
Input Validation
↓
FormState.validate()
↓
Is Data Valid?
↙ ↘
No Yes
↓ ↓
Show Errors Save Data
↓
Process Data
↓
API/Database
↓
Show Result
41. Important Form Methods
| Method | Purpose |
|---|
validate() | Runs validators and checks whether the form is valid. |
save() | Calls the onSaved callback for form fields. |
reset() | Resets fields and validation state. |
These operations are provided through FormState. :contentReference[oaicite:9]{index=9}
42. Important Form Properties and Callbacks
| Property/Callback | Purpose |
|---|
key | Identifies the form and provides access to its state when using a GlobalKey. |
validator | Checks whether an individual field contains valid input. |
onSaved | Receives a field's value when the form is saved. |
onChanged | Responds to changes within the form. |
onReset | Runs when an individual form field is reset. |
autovalidateMode | Controls automatic validation behavior. |
43. Interview Questions
Q1. What is the Form widget in Flutter?
Form is a widget used to group and manage multiple form fields and their state.
Q2. What is TextFormField?
TextFormField is a text input widget integrated with Flutter's form-field system and provides features such as validation and saving.
Q3. Why is GlobalKey used?
It provides a convenient way to access the current form state and call methods such as validate(), save(), and reset().
Q4. What does validator return?
It returns an error message when the input is invalid and null when the input is valid.
Q5. What does validate() return?
It returns true when the form has no validation errors and false when validation errors exist.
Q6. What is FormState.save() used for?
It invokes the onSaved callback for the form fields.
Q7. What does FormState.reset() do?
It resets the form fields and their validation state.
Q8. Why use TextEditingController in a form?
It provides direct access to the current text and allows the application to read, change, or clear the field programmatically.
Q9. What is autovalidateMode?
It controls when form fields automatically run their validation logic.
Q10. Why should controllers be disposed?
Controllers use resources and should be disposed when they are no longer needed.
44. Quick Revision
| Task | Code |
|---|
| Create Form Key | GlobalKey() |
| Create Form | Form(key: formKey, child: ...) |
| Create Form Field | TextFormField() |
| Validate Form | formKey.currentState!.validate() |
| Save Form | formKey.currentState!.save() |
| Reset Form | formKey.currentState!.reset() |
| Read Controller | controller.text |
| Clear Controller | controller.clear() |
| Validate Field | validator: (value) { ... } |
| Auto Validation | autovalidateMode |
| Save Field | onSaved: (value) { ... } |
45. Key Takeaways
- Flutter provides
Form for grouping and managing form fields.
TextFormField is useful for text input with form validation.
GlobalKey can be used to access the form state.
validate() checks all validators in the form.
save() invokes field-level onSaved callbacks.
reset() resets form fields and their validation state.
TextEditingController is useful when direct access to input is required.
FocusNode can be used to manage keyboard focus.
autovalidateMode controls automatic validation behavior.
- Input should be validated before processing or submitting it.
- Client-side validation improves user experience, but backend validation is still important.
- Controllers and focus nodes owned by stateful widgets should be disposed properly.
46. Official Flutter Resources
47. JustAcademy Flutter Training Resources
Learn more about Flutter development through the following resources: